// TupleSpaceUI.java

package com.ibm.tspaces.examples.tsui;

/*
**  Licensed Materials - Property of IBM
**
**  (C) COPYRIGHT IBM Corp. 1999  All rights reserved
**
**  US Government Users Restricted Rights - Use, duplication or
**  disclosure restricted by GSA ADP7 Schedule Contract with IBM Corp.
**
**/

import com.ibm.tspaces.*;
import java.io.*;
import com.ibm.tspaces.xtuples.*;      // for xml stuff
import com.ibm.tspaces.query.XMLQuery; // for xml stuff
import java.util.Vector;
import java.util.Enumeration; 


/***********************************************************************
** TupleSpaceUI **
*****************/
/**
** This class is a simple interface to a ts server.  It can be used to 
** manually read/write/take tuples from a ts server.  It is useful
** for debugging programs or manually interjecting tuples for 
** simulation purposes.  Also supported is basic XML support (courtesy Ben Zhao)
** the user can write an xml document (either in a file or inputted from the 
** command-line), and also perform an XQL query on the XML files stored in the
** tuplespace.
**
** 
** @version
** @author Thomas Fischer
*/

/*
  commands:
  dsc      (disconnect)
  con      (connect)
  w        (write)
  r        (read)
  rwait    (wait to read)
  t        (take)
  twait    (wait to take)
  s        (scan)
  sconsume (consuming scan)
  stat     (status)
  x        (exit)
  q        (quit, same as exit)
  h        (display command help)
  clear    (clear space)
  wxml     (write an xml document)
  qxml     (perform an xql query)
*/

public class TupleSpaceUI {

  public static final String DISCONNECT = "dsc";
  public static final String CONNECT = "con";
  public static final String WRITE = "w";
  public static final String READ = "r";
  public static final String WAITTOREAD = "rwait";
  public static final String TAKE = "t";
  public static final String WAITTOTAKE = "twait";
  public static final String SCAN = "s";
  public static final String CONSUMINGSCAN = "sconsume";
  public static final String EXIT = "x";
  public static final String QUIT = "q";
  public static final String HELP = "h";
  public static final String CLEAR = "clear";
  public static final String STATUS = "stat";
  public static final String WXML = "wxml";
  public static final String QXML = "qxml";
  public static final String XMLQUIT = ";"; //"<!--quit-->";
  public static final String TAGNAME = "tupleName";
  public static final String TAGDATA = "tupleData";
  public static final String TAGCONTENT = "tupleContent";
  public static final String INDENT_SIZE = "  ";
  public static final String NULL_CMD = "null_cmd";
  public static final int DEFAULT_PORT = 8200;
  public static final String DEFAULT_HOST= "localhost";
    
  private BufferedReader r;
  private TupleSpace ts;
  private String host, space;
  private int port, numTuples;
  
  /*******************************************************************
  ** TupleSpaceUI Constructor **
  *****************************/
  /**
  ** The noargs constructor for the UI
  */
  public TupleSpaceUI() {
    r = new BufferedReader(new InputStreamReader(System.in));
    ts = null;
  }
  
  /*******************************************************************
  ** displayHelp **
  ****************/
  /**
  ** Displays a list of commands that the user can execute.
  ** 
  */
  public void displayHelp() {
    System.out.println(
      "commands:\n"+
      "dsc        (disconnect)\n"+
      "con        (connect)\n"+
      "w          (write)\n"+
      "r          (read)\n"+
      "rwait      (wait to read)\n"+
      "t          (take)\n"+
      "twait      (wait to take)\n"+
      "s          (scan)\n"+
      "sconsume   (consuming scan)\n"+
      "stat       (status)\n"+
      "x          (exit)\n"+
      "q          (quit, same as exit)\n"+
      "h          (display command help)\n"+
      "clear      (clear space)\n"+
      "wxml       (xml write)\n"+
      "qxml       (xql query)\n"
    );
  } // end displayHelp 
  
  /*******************************************************************
  ** processCommands **
  ********************/
  /**
  ** The main command processing method.  This method starts the
  ** command processing loop.  This will determine the command
  ** type "XYZ" and call the appropriate "processXYZ" method
  ** the basic processXYZ methods will construct a tuple by invoking
  ** 'createATuple', to prompt the user to enter a tuple, the method
  ** will then perform the selected action on that tuple (read, write, take, etc)
  **
  ** @exception IOException 
  */
  public void processCommands() throws IOException{
    String cmd;
    //System.out.println("enter "+HELP+" for help");
    System.out.print("? ");
    cmd = r.readLine();
    while ((cmd != QUIT) && (cmd != EXIT)) {
      if (cmd.equals(DISCONNECT)) {
        processDisconnect();
      } else if (cmd.equals(CONNECT)) {
        processConnect();
      } else if (cmd.equals(WRITE)) {
        try {
          processWrite();
        }catch(TupleSpaceException e) {
          System.out.println("unable to write to "+ts.getName()+" on "+ts.getServer()+".");
        }
      } else if (cmd.equals(READ)) {
        try {
          processRead();
        }catch(TupleSpaceException e) {
          System.out.println("unable to read from "+ts.getName()+" on "+ts.getServer()+".");
        }
      } else if (cmd.equals(WAITTOREAD)) {
        try {
          processWaitToRead();
        }catch(TupleSpaceException e) {
          System.out.println("unable to read from "+ts.getName()+" on "+ts.getServer()+".");
        }
      } else if (cmd.equals(TAKE)) {
        try {
          processTake();
        }catch(TupleSpaceException e) {
          System.out.println("unable to take from "+ts.getName()+" on "+ts.getServer()+".");
        }
      } else if (cmd.equals(WAITTOTAKE)) {
        try {
          processWaitToTake();
        }catch(TupleSpaceException e) {
          System.out.println("unable to take from "+ts.getName()+" on "+ts.getServer()+".");
        }
      } else if (cmd.equals(SCAN)) {
        try {
          processScan();
        }catch(TupleSpaceException e) {
          System.out.println("unable to scan from "+ts.getName()+" on "+ts.getServer()+".");
        }
      } else if (cmd.equals(CONSUMINGSCAN)) {
        try {
          processConsumingScan();
        }catch(TupleSpaceException e) {
          System.out.println("unable to scan from "+ts.getName()+" on "+ts.getServer()+".");
        }
      } else if (cmd.equals(STATUS)) {
        if (ts != null) {
          processStatus();
        } else System.out.println("you must connect first.");
      } else if (cmd.equals(EXIT)) {
        processExit();
      } else if (cmd.equals(QUIT)) {
        processExit();
      } else if (cmd.equals(HELP)) {
        displayHelp();
      } else if (cmd.equals(CLEAR)) {
        try {
          processClear();
          System.out.println("space cleared.");
        }catch(TupleSpaceException e) {
          System.out.println("unable to clear space "+ts.getName()+" on "+ts.getServer()+".");
        }
      } else if (cmd.equals(WXML)) {
        try {
          processXMLWrite();
        }catch (TupleSpaceException e) {
          System.out.println("tuple space exception, unable to write xml document.");
          e.printStackTrace();
        }
      } else if (cmd.equals(QXML)) {
        try {
          processXMLQuery();
        }catch (TupleSpaceException e) {
          System.out.println("tuple space exception, unable to perform xql query.");
          e.printStackTrace();
        }
      } else if (cmd.equals("")) {
      } else {
        System.out.println("unrecognized commmand, enter "+HELP+" for help");
      }
      if (ts != null) {
        try {
          numTuples = ts.countN(new Tuple());
        }catch (TupleSpaceException e) {
          e.printStackTrace();
        }
        System.out.print(space+"@"+host+":"+port+"("+numTuples+") ? ");
      } else {
        System.out.print("? ");
      }
      cmd = r.readLine();
    }
  } // end processCommands 
  
  /*******************************************************************
  ** processDisconnect **
  **********************/
  /**
  ** disconnect from the TupleSpace.
  */  
  public void processDisconnect() {
    if (ts != null) {
      ts.cleanup();
      ts = null;
    } else {
      System.out.println("already disconnected.");
    }
  } // end processDisconnect 
  
  /*******************************************************************
  ** processConnect **
  *******************/
  /**
  ** Ask for the name, host and port of a TupleSpace and connect.
  ** @exception IOException
  */
  public void processConnect() throws IOException{
    String p;
    if (ts == null) {
      System.out.print("enter host (enter for "+DEFAULT_HOST+"): ");
      host = r.readLine();
      if (host.equals("")) {
        host = DEFAULT_HOST;
      }
      space = "";
      while (space.equals("")) {
        System.out.print("enter space: ");
        space = r.readLine();
      }
      System.out.print("enter port(enter for "+DEFAULT_PORT+"): ");
      try {
        p = r.readLine();
        port = Integer.parseInt(p);
      }catch (NumberFormatException e) {
        System.out.println("using default value of "+DEFAULT_PORT);
        port = DEFAULT_PORT;
      }
      
      System.out.println("connecting to "+space+" on "+host+":"+port+"...");
      try {
        ts = new TupleSpace(space, host, port);
        System.out.println("connected.");
      }catch (TupleSpaceException e) {
        System.out.println("unable to connect.");
        ts = null;
      }
      
    } else {
      System.out.println("already connected to "+ts.getName()+" on "+ts.getServer()+".");
      System.out.println("you must disconnect first");
    }
  } // end processConnect 
  
  /*******************************************************************
  ** processWrite **
  ******************/
  /**
  ** prompt for a tuple and write it to the connected space.
  ** @exception IOException
  ** @exception TupleSpaceException
  */
  public void processWrite() throws TupleSpaceException, IOException{
    Tuple t = createATuple(null);
    if (t == null) {
      System.out.println("canceled.");
      return;
    }
    if (ts != null) {
      ts.write(t);
      System.out.println("wrote tuple.");
    } else System.out.println("you must connect first");
  } // end processWrite 

  /*******************************************************************
  ** processRead **
  ****************/
  /**
  ** prompt for a tuple and read it from the connected space.
  ** @exception IOException
  ** @exception TupleSpaceException
  */
  public void processRead() throws TupleSpaceException, IOException{
    Tuple s, t = createATuple(null);
    if (t == null) {
      System.out.println("canceled.");
      return;
    }
    if (ts != null) {
      s=ts.read(t);
      if (s!= null) {
        System.out.println(s.toString());
      } else {
        System.out.println("no matching tuples.");
      }
    } else System.out.println("you must connect first");
  } // end processRead 
  
  /*******************************************************************
  ** processWaitToRead **
  **********************/
  /**
  ** prompt for a tuple and wait to read it from the connected space.
  ** @exception IOException
  ** @exception TupleSpaceException
  */
  public void processWaitToRead()throws TupleSpaceException, IOException{
    Tuple s, t = createATuple(null);
    if (t == null) {
      System.out.println("canceled.");
      return;
    }
    if (ts != null) {
      s=ts.waitToRead(t);
      if (s!= null) {
        System.out.println(s.toString());
      } else {
        System.out.println("no matching tuples.");
      }
    } else System.out.println("you must connect first");
  } // end processWaitToRead 
  
  /*******************************************************************
  ** processTake **
  ****************/
  /**
  ** prompt for a tuple and take it from the connected space.
  ** @exception IOException
  ** @exception TupleSpaceException
  */
  public void processTake()throws TupleSpaceException, IOException{
    Tuple s, t = createATuple(null);
    if (t == null) {
      System.out.println("canceled.");
      return;
    }
    if (ts != null) {
      s=ts.take(t);
      if (s!= null) {
        System.out.println(s.toString());
      } else {
        System.out.println("no matching tuples.");
      }
    } else System.out.println("you must connect first");
  } // end processTake 
  
  /*******************************************************************
  ** processWaitToTake **
  **********************/
  /**
  ** prompt for a tuple and wait to take it from the connected space.
  ** @exception IOException
  ** @exception TupleSpaceException
  */
  public void processWaitToTake()throws TupleSpaceException, IOException{
    Tuple s, t = createATuple(null);
    if (t == null) {
      System.out.println("canceled.");
      return;
    }
    if (ts != null) {
      s=ts.waitToTake(t);
      if (s!= null) {
        System.out.println(s.toString());
      } else {
        System.out.println("no matching tuples.");
      }
    } else System.out.println("you must connect first");
  } // end processWaitToTake 
  
  /*******************************************************************
  ** processScan **
  ****************/
  /**
  ** prompt for a tuple and scan it from the connected space.
  ** @exception IOException
  ** @exception TupleSpaceException
  */
  public void processScan()throws TupleSpaceException, IOException{
    Tuple s, t = createATuple(null);
    if (t == null) {
      System.out.println("canceled.");
      return;
    }
    if (ts != null) {
      s=ts.scan(t);
      if ((s != null) && (s.numberOfFields() > 0)){
        for (int i=0; i<s.numberOfFields(); i++) {
          System.out.println(((Tuple)s.getField(i).getValue()).toString());
        }
      } else {
        System.out.println("no matching tuples.");
      }
    } else System.out.println("you must connect first");
  } // end processScan 
  
  /*******************************************************************
  ** processConsumingScan **
  *************************/
  /**
  ** prompt for a tuple, scan it from the connected space and remove it.
  ** @exception IOException
  ** @exception TupleSpaceException
  */
  public void processConsumingScan()throws TupleSpaceException, IOException{
    Tuple s, t = createATuple(null);
    if (t == null) {
      System.out.println("canceled.");
      return;
    }
    if (ts != null) {
      s=ts.consumingScan(t);
      if ((s != null) && (s.numberOfFields() > 0)){
        for (int i=0; i<s.numberOfFields(); i++) {
          System.out.println(((Tuple)s.getField(i).getValue()).toString());
        }
      } else {
        System.out.println("no matching tuples.");
      }
    } else System.out.println("you must connect first");
  } // end processConsumingScan
  
  /*******************************************************************
  ** processStatus **
  ******************/
  /**
  ** display information about the connected space.
  */
  public void processStatus() {
    try {
      Tuple a = ts.status(ts.getServer());
      String status = (String)a.getField(0).getValue();
      String version = (String)a.getField(1).getValue();
      System.out.println("space name: "+ts.getName());
      System.out.println("host name: "+ts.getServer());
      System.out.println("port: "+ts.getPort());
      System.out.println("status: "+status);
      System.out.println("version: "+version);
      numTuples = ts.countN(new Tuple());
      System.out.println("number of tuples: "+numTuples);
      //System.out.println(a.toString());
    }catch(TupleSpaceException e) {
      System.out.println("unable to get status info from "+ts.getName()+" on "+ts.getServer()+".");
    }
  } // end processStatus
  
  /*******************************************************************
  ** processExit **
  ****************/
  /**
  ** Cleans up TupleSpace connections and exits the program
  */
  public void processExit() {
    if (ts != null) {
      ts.cleanup();
    }
    System.exit(0);
  } // end processExit
  
  /*******************************************************************
  ** processClear **
  *****************/
  /**
  ** clears the connected space.
  ** @exception TupleSpaceException
  */
  public void processClear() throws TupleSpaceException{
    if (ts != null) {
      ts.deleteAll();
    } else System.out.println("you must connect first");
  } // end processClear
  

  /****************************************************************************
  ** processXMLWrite **
  ********************/
  /**
  ** this handles the write of an XML document to a space, it makes use of Ben's 
  ** Xtuples module, which interprets a tuple with an "XMLField" in it as an XmlTuple,
  ** so this prompts the user for a filename or to type in a document and writes it.
  ** 
  */
  public void processXMLWrite() throws IOException, TupleSpaceException{
    if (ts != null) {
      System.out.print("file? (press enter to type in XML document): ");
      String curline, xmldoc, filename = r.readLine();
      if (filename.equals("")) {
        System.out.println("begin typing xml document, type '"+XMLQUIT+"' to exit and write");
        xmldoc = new String();
        curline = r.readLine();
        while(curline.indexOf(XMLQUIT) == -1) {
          xmldoc +=curline;
          curline = r.readLine();
        }
        xmldoc += curline.substring(0, curline.indexOf(XMLQUIT));
      } else {
        BufferedReader freader;
        try {
          freader = new BufferedReader(new FileReader(filename));
        }catch (FileNotFoundException e) {
          System.out.println("file not found, aborting");
          return;
        }
        xmldoc = new String();
        curline = freader.readLine();
        while (curline != null) {
          //-**/System.out.println(curline);
          xmldoc += curline;
          curline = freader.readLine();
        }
        freader.close();
      }
      //-**/System.out.println(xmldoc);
      ts.write(new Tuple(new XMLField(xmldoc)));
    } else System.out.println("you must connect first.");
  }
  
  /****************************************************************************
  ** processXMLQuery **
  ********************/
  /**
  ** this asks the user for a query in XQL to query the XML-tspaces data.
  ** it composes the query by creating a string in a XMLQuery and does a scan
  ** on this, returning a bunch of tuples, where each tuple represents the 
  ** node of a document that matched the query.  That is, if there are multiple
  ** documents with nodes that match the query, then they will all be printed out.
  ** 
  */
  public void processXMLQuery() throws IOException, TupleSpaceException{
    if (ts != null) {
      System.out.print("file? (press enter to type in a XQL query): ");
      String curline, xql, filename = r.readLine();
      if (filename.equals("")) {
        Tuple result;
        int quitIdx;
        boolean done = false;
        System.out.println("type "+XMLQUIT+XMLQUIT+" to end the query session");
        while (!done) {
          System.out.println("xmlquery? ("+XMLQUIT+" to quit)");
          xql = new String();
          curline = r.readLine();
          quitIdx = curline.indexOf(XMLQUIT);
          while (quitIdx == -1) {
            xql += curline;
            curline = r.readLine();
            quitIdx = curline.indexOf(XMLQUIT);
          }
          if (curline.length()-1 > quitIdx) {
            if (curline.substring(quitIdx+1, quitIdx+2).equals(XMLQUIT)) {
              done=true;
            }
          }
          xql += curline.substring(0, quitIdx);
          XQLQuery(xql);

         
        }
      } else {
        BufferedReader freader;
        Tuple result;
        try {
          freader = new BufferedReader(new FileReader(filename));
        }catch (FileNotFoundException e) {
          System.out.println("file not found, aborting");
          return;
        }
        xql = new String();
        curline = freader.readLine();
        while (curline != null) {
          System.out.println(curline);
          xql += curline;
          curline = freader.readLine();
        }
        freader.close();
        XQLQuery(xql);
      }
    } else System.out.println("you must connect first.");
  }
  
  
  /****************************************************************************
  ** XQLQuery **
  ****************/
  /**
  ** Issue a XQL Query
  ** @param xql  The query string
  ** 
  */
  public void XQLQuery( String xql) throws TupleSpaceException{
    Tuple result;
    result = ts.scan(new XMLQuery(xql));
    // result is a tuple where each field is one of the 
    // original tuples written with the original 
    // xmlField filled in with the query result info
    for (int i=0; i<result.numberOfFields(); i++) {
      SuperTuple rTuple = (SuperTuple)result.getField(i).getValue();
      for (int j=0; j<rTuple.numberOfFields(); j++) { 
        if (rTuple.getField(j) instanceof XMLField) { 
          XMLField xField = (XMLField)rTuple.getField(j);
          Vector v = xField.getQueryResult();
          Enumeration e = v.elements();
          while (e.hasMoreElements()) {
            TupleID tid = (TupleID)e.nextElement();
            parseXTuple(tid, "");
          }
        }
      }
    }
  
  }
  /****************************************************************************
  ** parseXTuple **
  ****************/
  /**
  ** The recursive method that takes a xml-tuple (which represents a node of an
  ** XML document, DOM-style), and prints the tag, the attributes, the value and its
  ** children.
  **
  ** @param tuple the node to print out
  ** @param indent the number of spaces to indent (to make the document pretty)
  */
  public void parseXTuple(TupleID tid, String indent) throws TupleSpaceException{
    SuperTuple tuple = ts.readTupleById(tid); 
    XTuple xt = (XTuple)tuple.getField(TAGDATA).getValue();
    System.out.print(indent+"<"+(String)tuple.getField(TAGNAME).getValue());
    Enumeration e = xt.getAttributes().elements();
    TAttr ta;
    while (e.hasMoreElements()) {
      ta = (TAttr) e.nextElement();
      System.out.print(" "+ta.getName()+"=\""+ta.getValue()+"\"");
    }
    System.out.println(">");
    try {
      if (!((String) tuple.getField(TAGCONTENT).getValue()).equals("")) {
        System.out.println(indent+INDENT_SIZE+(String)tuple.getField(TAGCONTENT).getValue());
      }
    }catch (NullPointerException ex) {
    }catch (TupleSpaceException ex) {
    }
    
    Enumeration children;
    if (xt.hasChildTuples()) {
      children = xt.getChildTupleIds().elements();
      //Tuple child;
      while (children.hasMoreElements()) {
        //parseXTuple((Tuple)ts.readTupleById((TupleID)children.nextElement()), indent+INDENT_SIZE);
        parseXTuple((TupleID)children.nextElement(), indent+INDENT_SIZE);
      }
    }
    System.out.println(indent+"</"+(String)tuple.getField(TAGNAME).getValue()+">");
  }
  
  //\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
  //\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
  //\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ tuple submenu \/\/\/\/\/\/\/\/\/\/\/\/\/\/\
  
  /* These are the valid command for the tuple submenu
    valid commands:
    i    (int)
    d    (double)
    b    (boolean)
    s    (String)
    t    (Tuple)
    q    (back)
    x    (back, same as q)
    c    (cancel)
    chg    (change a field)
    h    (help)
    show  (show tuple)
  */
  
  public static final String INT = "i";
  public static final String DOUBLE = "d";
  public static final String STRING = "s";
  public static final String BOOLEAN = "b";
  public static final String TUPLE = "t";
  public static final String CHANGE = "chg";
  public static final String SHOW = "show";
  public static final String CANCEL = "c";
  public static final String EDIT = "e";
  
  public static final String TRUE = "t";
  public static final String FALSE = "f";

  /*******************************************************************
  ** copy **
  *********/
  /**
  ** copies a tuple recursively.  All references in all fields of the tuple will
  ** be copied.  The method will descend into nested tuples and copy all fields of 
  ** those tuples also.
  ** @param s the tuple to copy
  ** @return a copy of the argument tuple
  ** @exception TupleSpaceException
  */
  public Tuple copy(Tuple s) throws TupleSpaceException{
    Tuple t = new Tuple();
    for (int i=0; i<s.numberOfFields(); i++) {
      if (s.getField(i).getType().equals(Tuple.class)) {
        t.add(copy((Tuple)s.getField(i).getValue()));
      }
      t.add(s.getField(i));
    }
    return t;
  } // end copy

  /*******************************************************************
  ** createATuple **
  *****************/
  /**
  ** display the tuple creation menu and prompt for a tuple creation command.
  ** This includes:  adding integers, doubles, booleans, tuples and strings.
  ** This is a recursive method, it returns the tuple it created, and it 
  ** takes as an arguement the tuple that it should modify.  If the arg is null,
  ** it will create a new tuple.  When a tuple is created in a field, this
  ** method will be called to recursively descend into that tuple and 
  ** modify/create fields within it. Typing 'x' will back out a tuple, if this
  ** is the only tuple, the selected action (read, write, etc) will be performed 
  ** on that tuple.  You can also change a tuple's fields, (if you make a mistake)
  ** or you can cancel the whole operation.  Note that if you are writing tuples with
  ** any degree of complexity, it would be easier to write a simple program, since
  ** there is no functionality to save tuples (or tuple formats) that you have made 
  ** previously.  
  **
  ** @param s the tuple to modify/null if a new one is to be created.
  ** @return the modified/created tuple
  ** @exception IOException
  ** @exception TupleSpaceException
  */
  public Tuple createATuple(Tuple s) throws IOException, TupleSpaceException{
    Tuple t;
    if (s == null) {
      t = new Tuple();
    } else t = copy(s);
    String cmd, sc;
    boolean isValid;
    System.out.print("**tuple menu**\n command? ");
    cmd = r.readLine();
    while ((cmd != QUIT) && (cmd != EXIT)) {
      if (cmd.equals(INT) || cmd.equals(DOUBLE) || cmd.equals(STRING) || 
          cmd.equals(BOOLEAN) || cmd.equals(TUPLE)) {
        addField(t, cmd);
      } else if (cmd.equals(CHANGE)) {
        if (t.numberOfFields() >=1) {
          isValid = false;
          sc = NULL_CMD;
          Integer field = null;
            // first prompt for a field number to edit
          while ((sc != QUIT) && (sc != EXIT) && !isValid) {
            System.out.print("enter a field up to "+(t.numberOfFields()-1)+": ");
            sc = r.readLine();
            try {
              field = new Integer(sc);
              isValid = true;
            }catch (NumberFormatException e) {
              System.out.print("enter a field up to "+(t.numberOfFields()-1)+": ");
              sc = r.readLine();
            }
          }
          if ((sc != QUIT) && (sc != EXIT)) {
            // if the field to be edited is a tuple, then we have to ask him if
            // he wants to edit THAT field, or descend into it and edit the fields
            // within the tuple.
            if (t.getField(field.intValue()).getType().equals(Tuple.class)) {
              System.out.println("field is a tuple, type e to edit it, or");
            }
            // then prompt for a type
            System.out.print("enter a type(i,d,b,s,t): ");
            sc = r.readLine();
            if (sc.equals(EDIT)) {  // we invoke 'createATuple' with a non-null arg to edit that sub-tuple
              t.setField(field.intValue(), new Field(
                createATuple((Tuple)t.getField(field.intValue()).getValue())
              ));
            }else {  // else we change the whole field to a new type + value.
              changeField(t, sc, field.intValue());
            }
          }
        } else {
          System.out.println("no fields to change.");
        }
      } else if (cmd.equals(CANCEL)) {
        return s;
      } else if (cmd.equals(HELP)) {
        System.out.println(
          "valid commands:\n"+
          "i      (int)\n"+
          "d      (double)\n"+
          "b      (boolean)\n"+
          "s      (String)\n"+
          "t      (Tuple)\n"+
          "q      (back)\n"+
          "x      (back, same as q)\n"+
          "c      (cancel)\n"+
          "chg    (change a field)\n"+
          "h      (help)\n"+
          "show   (show tuple)\n"
        );
      } else if (cmd.equals(EXIT)) {
        return t;
      } else if (cmd.equals(QUIT)) {
        return t;
      } else if (cmd.equals(SHOW)) {
        System.out.println(t.toString());
      } else if (cmd.equals("")) {
      } else {
        System.out.println("unrecognized command, press "+HELP+" for help");
      }
      System.out.print("**tuple menu**\n command? ");
      cmd = r.readLine();
    }
    return t;
  } // end createATuple
  
  /*******************************************************************
  ** addField **
  *************/
  /**
  ** add a field of type indicated by cmd to the Tuple t.  If the field to 
  ** be added is a tuple, then recursively call createATuple to modify
  ** that new tuple.
  ** @param t the tuple to add fields to
  ** @param cmd the type of the field to be added.
  ** @exception TupleSpaceException
  ** @exception IOException
  */
  public void addField(Tuple t, String cmd) throws TupleSpaceException, IOException{
    boolean isValid;
    String value;
    if (cmd.equals(INT)) {
      isValid = false;
      Integer val;
      System.out.print("enter int value (enter for template): ");
      value = r.readLine();
      while ((value != QUIT) && (value != EXIT) && !isValid) {
        if (value.equals("")) {
          t.add(new Field(Integer.class));
          isValid = true;
        } else {
          try {
            val = new Integer(value);
            isValid = true;
            t.add(new Field(val));
          }catch(NumberFormatException e) {
            System.out.print("please enter an int.(digits 1234567890, no other chars)");
            value = r.readLine();
          }
        }
      }
    } else if (cmd.equals(DOUBLE)) {
      isValid = false;
      Double val;
      System.out.print("enter double value (enter for template): ");
      value = r.readLine();
      while ((value != QUIT) && (value != EXIT) && !isValid) {
        if (value.equals("")) {
          t.add(new Field(Double.class));
          isValid = true;
        } else {
          try {
            val = new Double(value);
            isValid = true;
            t.add(new Field(val));
          }catch(NumberFormatException e) {
            System.out.println("please enter an double.(digits 1234567890 and a decimal point)");
            value = r.readLine();
          }
        }
      }
    } else if (cmd.equals(STRING)) {
      System.out.print("enter String value (enter for template): ");
      value = r.readLine();
      if (value.equals("")) {
        t.add(new Field(String.class));
      } else t.add(new Field(value));
    } else if (cmd.equals(BOOLEAN)) {
      isValid = false;
      System.out.print("enter boolean value (t/f/nothing for template): ");
      value = r.readLine();
      while ((value != QUIT) && (value != EXIT) && !isValid) {
        if (value.equals("")) {
          t.add(new Field(Boolean.class));
          isValid = true;
        } else {
          if (value.equals(TRUE)) {
            t.add(new Field(new Boolean(true)));
            isValid = true;
          } else if (value.equals(FALSE)) {
            t.add(new Field(new Boolean(false)));
            isValid = true;
          } else {
            System.out.println("enter "+TRUE+" or "+FALSE+".");
            System.out.print("enter boolean value (t/f/nothing for template): ");
            value = r.readLine();
          }
        }
      }
    } else if (cmd.equals(TUPLE)) {
      System.out.println("descending into new tuple...");
      t.add(new Field(createATuple(null)));
    } else System.out.println("unrecognized type");
  } // end addField
  
  /*******************************************************************
  ** changeField **
  *****************/
  /**
  ** Alter the field in the tuple t at index i to the type indicated by cmd.  
  ** If the type is a tuple, then recursively descend into that tuple and 
  ** alter it.
  ** @param t the tuple to modify
  ** @param cmd the new type of the field
  ** @param i the index of the field to be modified in the tuple t.
  ** @exception IOException
  ** @exception TupleSpaceException
  */
  public void changeField(Tuple t, String cmd, int i) throws TupleSpaceException, IOException{
    boolean isValid;
    String value;
    if (cmd.equals(INT)) {
      isValid = false;
      Integer val;
      System.out.print("enter int value (enter for template): ");
      value = r.readLine();
      while ((value != QUIT) && (value != EXIT) && !isValid) {
        if (value.equals("")) {
          t.setField(i, new Field(Integer.class));
          isValid = true;
        } else {
          try {
            val = new Integer(value);
            isValid = true;
            t.setField(i, new Field(val));
          }catch(NumberFormatException e) {
            System.out.print("please enter an int.(digits 1234567890, no other chars)");
            value = r.readLine();
          }
        }
      }
    } else if (cmd.equals(DOUBLE)) {
      isValid = false;
      Double val;
      System.out.print("enter double value (enter for template): ");
      value = r.readLine();
      while ((value != QUIT) && (value != EXIT) && !isValid) {
        if (value.equals("")) {
          t.setField(i, new Field(Double.class));
          isValid = true;
        } else {
          try {
            val = new Double(value);
            isValid = true;
            t.setField(i, new Field(val));
          }catch(NumberFormatException e) {
            System.out.println("please enter an double.(digits 1234567890 and a decimal point)");
            value = r.readLine();
          }
        }
      }
    } else if (cmd.equals(STRING)) {
      System.out.print("enter String value (enter for template): ");
      value = r.readLine();
      if (value.equals("")) {
        t.setField(i, new Field(String.class));
      } else t.setField(i, new Field(value));
    } else if (cmd.equals(BOOLEAN)) {
      isValid = false;
      System.out.print("enter boolean value (t/f/nothing for template): ");
      value = r.readLine();
      while ((value != QUIT) && (value != EXIT) && !isValid) {
        if (value.equals("")) {
          t.setField(i, new Field(Boolean.class));
          isValid = true;
        } else {
          if (value.equals(TRUE)) {
            t.setField(i, new Field(new Boolean(true)));
            isValid = true;
          } else if (value.equals(FALSE)) {
            t.setField(i, new Field(new Boolean(false)));
            isValid = true;
          } else {
            System.out.println("enter "+TRUE+" or "+FALSE+".");
            System.out.print("enter boolean value (t/f/nothing for template): ");
            value = r.readLine();
          }
        }
      }
    } else if (cmd.equals(TUPLE)) {
      System.out.println("descending into new tuple...");
      t.setField(i, new Field(createATuple(null)));
    } else System.out.println("unrecognized type.");
  } // end changeField
  
  /*******************************************************************
  ** main **
  *********/
  /**
  ** The main method creates a new TupleSpaceUI instance, and calls the 
  ** processCommands method.
  ** @param args ignored.
  ** @exception IOException 
  */
  public static void main (String args[]) throws IOException{
    TupleSpaceUI tsui = new TupleSpaceUI();
    tsui.processCommands();
  } // end main
} // end class TupleSpaceUI